

package com.hazelcast.config;

import com.hazelcast.config.CacheSimpleConfig.ExpiryPolicyFactoryConfig;
import com.hazelcast.config.CacheSimpleConfig.ExpiryPolicyFactoryConfig.DurationConfig;
import com.hazelcast.config.CacheSimpleConfig.ExpiryPolicyFactoryConfig.TimedExpiryPolicyFactoryConfig;
import com.hazelcast.config.cp.*;
import com.hazelcast.config.security.*;
import com.hazelcast.config.tpc.TpcConfig;
import com.hazelcast.config.vector.VectorCollectionConfig;
import com.hazelcast.core.HazelcastException;
import com.hazelcast.instance.EndpointQualifier;
import com.hazelcast.internal.config.AliasedDiscoveryConfigUtils;
import com.hazelcast.internal.config.ServicesConfig;
import com.hazelcast.internal.util.CollectionUtil;

import java.util.*;
import java.util.Map.Entry;

import static com.hazelcast.internal.config.AliasedDiscoveryConfigUtils.aliasedDiscoveryConfigsFrom;
import static com.hazelcast.internal.config.ConfigUtils.lookupByPattern;
import static java.text.MessageFormat.format;
import static java.util.Collections.singletonMap;
import static org.codehaus.groovy.runtime.InvokerHelper.asList;
import static org.junit.Assert.assertEquals;

public class ConfigCompatibilityChecker {

    /**
     * Checks if two {@link Config} instances are compatible.
     * <p>
     * This mostly means that the config values will have the same impact on the behaviour of the system,
     * but are not necessarily the same (e.g. a {@code null} value is sometimes the same as an empty
     * collection or a disabled config).
     *
     * <b>Note:</b> This method checks MOST but NOT ALL configuration. As such it is best used in test
     * scenarios to cover as much config checks as possible automatically.
     *
     * @param c1 the {@link Config} to check
     * @param c2 the {@link Config} to check
     * @return {@code true} if the configs are compatible, {@code false} otherwise
     * @throws HazelcastException       if configs are incompatible
     * @throws IllegalArgumentException if one of the configs is {@code null}
     */
    static boolean isCompatible(Config c1, Config c2) {
        if (c1 == c2) {
            return true;
        }
        if (c1 == null || c2 == null) {
            throw new IllegalArgumentException("One of the two configs is null");
        }
        if (!nullSafeEqual(c1.getClusterName(), c2.getClusterName())) {
            return false;
        }

        checkWanConfigs(c1.getWanReplicationConfigs(), c2.getWanReplicationConfigs());
        checkCompatibleConfigs("partition group", c1.getPartitionGroupConfig(), c2.getPartitionGroupConfig(), new PartitionGroupConfigChecker());
        checkCompatibleConfigs("serialization", c1.getSerializationConfig(), c2.getSerializationConfig(), new SerializationConfigChecker());
        checkCompatibleConfigs("management center", c1.getManagementCenterConfig(), c2.getManagementCenterConfig(), new ManagementCenterConfigChecker());
        checkCompatibleConfigs("hot restart", c1.getHotRestartPersistenceConfig(), c2.getHotRestartPersistenceConfig(), new HotRestartConfigChecker());
        checkCompatibleConfigs("persistence", c1.getPersistenceConfig(), c2.getPersistenceConfig(), new PersistenceConfigChecker());
        checkCompatibleConfigs("device", c1, c2, c1.getDeviceConfigs(), c2.getDeviceConfigs(), new DeviceConfigChecker());
        checkCompatibleConfigs("CRDT replication", c1.getCRDTReplicationConfig(), c2.getCRDTReplicationConfig(), new CRDTReplicationConfigChecker());
        checkCompatibleConfigs("network", c1.getNetworkConfig(), c2.getNetworkConfig(), new NetworkConfigChecker());
        checkCompatibleConfigs("advanced network", c1.getAdvancedNetworkConfig(), c2.getAdvancedNetworkConfig(), new AdvancedNetworkConfigChecker());
        checkCompatibleConfigs("map", c1, c2, c1.getMapConfigs(), c2.getMapConfigs(), new MapConfigChecker());
        checkCompatibleConfigs("ringbuffer", c1, c2, c1.getRingbufferConfigs(), c2.getRingbufferConfigs(), new RingbufferConfigChecker());
        checkCompatibleConfigs("queue", c1, c2, c1.getQueueConfigs(), c2.getQueueConfigs(), new QueueConfigChecker());
        checkCompatibleConfigs("topic", c1, c2, c1.getTopicConfigs(), c2.getTopicConfigs(), new TopicConfigChecker());
        checkCompatibleConfigs("reliable topic", c1, c2, c1.getReliableTopicConfigs(), c2.getReliableTopicConfigs(), new ReliableTopicConfigChecker());
        checkCompatibleConfigs("cache", c1, c2, c1.getCacheConfigs(), c2.getCacheConfigs(), new CacheSimpleConfigChecker());
        checkCompatibleConfigs("durable executor", c1, c2, c1.getDurableExecutorConfigs(), c2.getDurableExecutorConfigs(), new DurableExecutorConfigChecker());
        checkCompatibleConfigs("scheduled executor", c1, c2, c1.getScheduledExecutorConfigs(), c2.getScheduledExecutorConfigs(), new ScheduledExecutorConfigChecker());
        checkCompatibleConfigs("multimap", c1, c2, c1.getMultiMapConfigs(), c2.getMultiMapConfigs(), new MultimapConfigChecker());
        checkCompatibleConfigs("replicated map", c1, c2, c1.getReplicatedMapConfigs(), c2.getReplicatedMapConfigs(), new ReplicatedMapConfigChecker());
        checkCompatibleConfigs("list", c1, c2, c1.getListConfigs(), c2.getListConfigs(), new ListConfigChecker());
        checkCompatibleConfigs("set", c1, c2, c1.getSetConfigs(), c2.getSetConfigs(), new SetConfigChecker());
        checkCompatibleConfigs("flake id generator", c1, c2, c1.getFlakeIdGeneratorConfigs(), c2.getFlakeIdGeneratorConfigs(), new FlakeIdGeneratorConfigChecker());
        checkCompatibleConfigs("cardinality estimator", c1, c2, c1.getCardinalityEstimatorConfigs(), c2.getCardinalityEstimatorConfigs(), new CardinalityEstimatorConfigChecker());
        checkCompatibleConfigs("pn counter", c1, c2, c1.getPNCounterConfigs(), c2.getPNCounterConfigs(), new PNCounterConfigChecker());
        checkCompatibleConfigs("split-brain-protection", c1, c2, c1.getSplitBrainProtectionConfigs(), c2.getSplitBrainProtectionConfigs(), new SplitBrainProtectionConfigChecker());
        checkCompatibleConfigs("security", c1, c2, singletonMap("", c1.getSecurityConfig()), singletonMap("", c2.getSecurityConfig()), new SecurityConfigChecker());
        checkCompatibleConfigs("cp subsystem", c1, c2, singletonMap("", c1.getCPSubsystemConfig()), singletonMap("", c2.getCPSubsystemConfig()), new CPSubsystemConfigChecker());
        checkCompatibleConfigs("auditlog", c1, c2, singletonMap("", c1.getAuditlogConfig()), singletonMap("", c2.getAuditlogConfig()), new AuditlogConfigChecker());

        checkCompatibleConfigs("metrics", c1.getMetricsConfig(), c2.getMetricsConfig(), new MetricsConfigChecker());
        checkCompatibleConfigs("sql", c1.getSqlConfig(), c2.getSqlConfig(), new SqlConfigChecker());

        checkCompatibleConfigs("instance-tracking", c1.getInstanceTrackingConfig(), c2.getInstanceTrackingConfig(), new InstanceTrackingConfigChecker());
        checkCompatibleConfigs("native memory", c1.getNativeMemoryConfig(), c2.getNativeMemoryConfig(), new NativeMemoryConfigChecker());
        checkCompatibleConfigs("data connection", c1, c2, c1.getDataConnectionConfigs(), c2.getDataConnectionConfigs(), new DataConnectionConfigChecker());
        checkCompatibleConfigs("tpc", c1, c2, singletonMap("", c1.getTpcConfig()), singletonMap("", c2.getTpcConfig()), new TpcConfigChecker());
        checkCompatibleConfigs("user-code-namespaces", c1, c2, singletonMap("", c1.getNamespacesConfig()), singletonMap("", c2.getNamespacesConfig()), new UserCodeNamespacesConfigChecker());
        checkCompatibleConfigs("vector collections", c1.getVectorCollectionConfigs(), c2.getVectorCollectionConfigs(), new VectorCollectionConfigsChecker());
        return true;
    }

    public static void checkWanConfigs(Map<String, WanReplicationConfig> c1, Map<String, WanReplicationConfig> c2) {
        if ((c1 != c2 && (c1 == null || c2 == null)) || c1.size() != c2.size()) {
            throw new HazelcastException(format("Incompatible wan replication config :\n{0}\n vs \n{1}", c1, c2));
        }
        WanReplicationConfigChecker checker = new WanReplicationConfigChecker();
        for (Entry<String, WanReplicationConfig> entry : c1.entrySet()) {
            checkCompatibleConfigs("wan replication", entry.getValue(), c2.get(entry.getKey()), checker);
        }
    }

    public static void checkEndpointConfigCompatible(EndpointConfig c1, EndpointConfig c2) {
        checkCompatibleConfigs("endpoint-config", c1, c2, new EndpointConfigChecker());
    }

    private static <T> void checkCompatibleConfigs(String type, T c1, T c2, ConfigChecker<T> checker) {
        if (!checker.check(c1, c2)) {
            throw new HazelcastException(format("Incompatible " + type + " config :\n{0}\n vs \n{1}", c1, c2));
        }
    }

    private static <T> void checkCompatibleConfigs(String type, Config c1, Config c2, Map<String, T> configs1, Map<String, T> configs2, ConfigChecker<T> checker) {
        Set<String> configNames = new HashSet<>(configs1.keySet());
        configNames.addAll(configs2.keySet());

        for (String name : configNames) {
            T config1 = lookupByPattern(c1.getConfigPatternMatcher(), configs1, name);
            T config2 = lookupByPattern(c2.getConfigPatternMatcher(), configs2, name);
            if (config1 != null && config2 != null && !checker.check(config1, config2)) {
                throw new HazelcastException(format("Incompatible " + type + " config :\n{0}\n vs \n{1}", config1, config2));
            }
        }
        T config1 = checker.getDefault(c1);
        T config2 = checker.getDefault(c2);
        if (!checker.check(config1, config2)) {
            throw new HazelcastException(format("Incompatible default " + type + " config :\n{0}\n vs \n{1}", config1, config2));
        }
    }

    private static boolean nullSafeEqual(Object a, Object b) {
        return Objects.equals(a, b);
    }

    private static <T> boolean isCollectionCompatible(Collection<T> c1, Collection<T> c2, ConfigChecker<T> checker) {
        if (c1 == c2) {
            return true;
        }
        if (c1 == null || c2 == null || c1.size() != c2.size()) {
            return false;
        }

        Iterator<T> i1 = c1.iterator();
        Iterator<T> i2 = c2.iterator();
        while (i1.hasNext() && i2.hasNext()) {
            T config1 = i1.next();
            T config2 = i2.next();
            if (!checker.check(config1, config2)) {
                return false;
            }
        }
        return !(i1.hasNext() || i2.hasNext());
    }

    private static boolean isCompatible(EvictionConfig c1, EvictionConfig c2) {
        return new EvictionConfigChecker().check(c1, c2);
    }

    private static boolean isCompatible(PredicateConfig c1, PredicateConfig c2) {
        return new PredicateConfigChecker().check(c1, c2);
    }

    private static boolean isCompatible(CollectionConfig c1, CollectionConfig c2) {
        return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getItemListenerConfigs(), c2.getItemListenerConfigs()) && nullSafeEqual(c1.getBackupCount(), c2.getBackupCount()) && nullSafeEqual(c1.getAsyncBackupCount(), c2.getAsyncBackupCount()) && nullSafeEqual(c1.getMaxSize(), c2.getMaxSize()) && nullSafeEqual(c1.isStatisticsEnabled(), c2.isStatisticsEnabled()) && nullSafeEqual(c1.getSplitBrainProtectionName(), c2.getSplitBrainProtectionName()) && isCompatible(c1.getMergePolicyConfig(), c2.getMergePolicyConfig());
    }

    private static boolean isCompatible(MerkleTreeConfig c1, MerkleTreeConfig c2) {
        boolean c1Disabled = c1 == null || !c1.isEnabled();
        boolean c2Disabled = c2 == null || !c2.isEnabled();
        return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && c1.getDepth() == c2.getDepth());
    }

    private static boolean isCompatible(HotRestartConfig c1, HotRestartConfig c2) {
        boolean c1Disabled = c1 == null || !c1.isEnabled();
        boolean c2Disabled = c2 == null || !c2.isEnabled();
        return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(c1.isFsync(), c2.isFsync()));
    }

    private static boolean isCompatible(EventJournalConfig c1, EventJournalConfig c2) {
        boolean c1Disabled = c1 == null || !c1.isEnabled();
        boolean c2Disabled = c2 == null || !c2.isEnabled();
        return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(c1.getCapacity(), c2.getCapacity()) && nullSafeEqual(c1.getTimeToLiveSeconds(), c2.getTimeToLiveSeconds()));
    }

    private static boolean isCompatible(MergePolicyConfig c1, MergePolicyConfig c2) {
        return c1 == c2 || !(c1 == null || c2 == null) && c1.getBatchSize() == c2.getBatchSize() && nullSafeEqual(c1.getPolicy(), c2.getPolicy());
    }

    public static void checkQueueConfig(QueueConfig expectedConfig, QueueConfig actualConfig) {
        checkCompatibleConfigs("queue-config", expectedConfig, actualConfig, new QueueConfigChecker());
    }

    public static void checkListenerConfigs(List<ListenerConfig> expectedConfigs, List<ListenerConfig> actualConfigs) {
        assertEquals(expectedConfigs.size(), actualConfigs.size());
        for (int i = 0; i < expectedConfigs.size(); i++) {
            checkCompatibleConfigs("listener-config", expectedConfigs.get(i), actualConfigs.get(i), new ReplicatedMapListenerConfigChecker());
        }
    }

    public static void checkSocketInterceptorConfig(SocketInterceptorConfig expectedConfig, SocketInterceptorConfig actualConfig) {
        checkCompatibleConfigs("socket-interceptor-config", expectedConfig, actualConfig, new SocketInterceptorConfigChecker());
    }

    public static void checkMapConfig(MapConfig expectedConfig, MapConfig actualConfig) {
        checkCompatibleConfigs("map-config", expectedConfig, actualConfig, new MapConfigChecker());
    }

    public static void checkDynamicConfigurationConfig(DynamicConfigurationConfig expectedConfig, DynamicConfigurationConfig actualConfig) {
        checkCompatibleConfigs("dynamic-configuration-config", expectedConfig, actualConfig, new DynamicConfigurationConfigChecker());
    }

    public static void checkDeviceConfig(LocalDeviceConfig expectedConfig, LocalDeviceConfig actualConfig) {
        checkCompatibleConfigs("device-config", expectedConfig, actualConfig, new DeviceConfigChecker());
    }

    public static void checkRingbufferConfig(RingbufferConfig expectedConfig, RingbufferConfig actualConfig) {
        checkCompatibleConfigs("ringbuffer", expectedConfig, actualConfig, new RingbufferConfigChecker());
    }

    public static void checkMemberAddressProviderConfig(MemberAddressProviderConfig expected, MemberAddressProviderConfig actual) {
        checkCompatibleConfigs("member-address-provider", expected, actual, new MemberAddressProviderConfigChecker());
    }

    public static void checkSerializerConfigs(Collection<SerializerConfig> expected, Collection<SerializerConfig> actual) {
        assertEquals(expected.size(), actual.size());
        Iterator<SerializerConfig> expectedIterator = expected.iterator();
        Iterator<SerializerConfig> actualIterator = actual.iterator();
        SerializerConfigChecker checker = new SerializerConfigChecker();
        while (expectedIterator.hasNext()) {
            checkCompatibleConfigs("serializer", expectedIterator.next(), actualIterator.next(), checker);
        }
    }

    public static void checkSSLConfig(SSLConfig expected, SSLConfig actual) {
        checkCompatibleConfigs("ssl", expected, actual, new SSLConfigChecker());
    }

    public static void checkQueryCacheConfig(QueryCacheConfig expected, QueryCacheConfig actual) {
        checkCompatibleConfigs("query-cache", expected, actual, new QueryCacheConfigChecker());
    }

    public static void checkNearCacheConfig(NearCacheConfig expected, NearCacheConfig actual) {
        checkCompatibleConfigs("near-cache", expected, actual, new NearCacheConfigChecker());
    }

    private static String classNameOrImpl(String className, Object impl) {
        return impl != null ? impl.getClass().getName() : className;
    }

    private static String classNameOrClass(String className, Class<?> clazz) {
        return clazz != null ? clazz.getName() : className;
    }

    private static boolean contains(Object[] values, Object toFind) {
        if (values == null || values.length == 0) {
            return false;
        }
        for (int i = 0; i < values.length; i++) {
            if (toFind == values[i] || (toFind != null && toFind.equals(values[i]))) {
                return true;
            }
        }
        return false;
    }

    private abstract static class ConfigChecker<T> {

        abstract boolean check(T t1, T t2);

        T getDefault(Config c) {
            return null;
        }
    }

    private static class NearCacheConfigChecker extends ConfigChecker<NearCacheConfig> {
        @Override
        boolean check(NearCacheConfig c1, NearCacheConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && c1.isCacheLocalEntries() == c2.isCacheLocalEntries() && c1.isSerializeKeys() == c2.isSerializeKeys() && c1.isInvalidateOnChange() == c2.isInvalidateOnChange() && c1.getTimeToLiveSeconds() == c2.getTimeToLiveSeconds() && c1.getMaxIdleSeconds() == c2.getMaxIdleSeconds() && c1.getInMemoryFormat() == c2.getInMemoryFormat() && c1.getLocalUpdatePolicy() == c2.getLocalUpdatePolicy() && isCompatible(c1.getEvictionConfig(), c2.getEvictionConfig()) && nullSafeEqual(c1.getPreloaderConfig(), c2.getPreloaderConfig()));
        }
    }

    private static class QueryCacheConfigChecker extends ConfigChecker<QueryCacheConfig> {
        @Override
        boolean check(QueryCacheConfig c1, QueryCacheConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && c1.getBatchSize() == c2.getBatchSize() && c1.getBufferSize() == c2.getBufferSize() && c1.getDelaySeconds() == c2.getDelaySeconds() && c1.isIncludeValue() == c2.isIncludeValue() && c1.isPopulate() == c2.isPopulate() && c1.isCoalesce() == c2.isCoalesce() && c1.getInMemoryFormat() == c2.getInMemoryFormat() && isCompatible(c1.getEvictionConfig(), c2.getEvictionConfig()) && isCollectionCompatible(c1.getEntryListenerConfigs(), c2.getEntryListenerConfigs(), new EntryListenerConfigChecker()) && isCollectionCompatible(c1.getIndexConfigs(), c2.getIndexConfigs(), new IndexConfigChecker()) && isCompatible(c1.getPredicateConfig(), c2.getPredicateConfig()));
        }
    }

    private static class PredicateConfigChecker extends ConfigChecker<PredicateConfig> {
        @Override
        boolean check(PredicateConfig c1, PredicateConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && nullSafeEqual(classNameOrImpl(c1.getClassName(), c1.getImplementation()), classNameOrImpl(c2.getClassName(), c2.getImplementation())) && nullSafeEqual(c1.getSql(), c2.getSql()));
        }
    }

    private static class EvictionConfigChecker extends ConfigChecker<EvictionConfig> {
        @Override
        boolean check(EvictionConfig c1, EvictionConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && c1.getSize() == c2.getSize() && c1.getMaxSizePolicy() == c2.getMaxSizePolicy() && c1.getEvictionPolicy() == c2.getEvictionPolicy() && nullSafeEqual(classNameOrImpl(c1.getComparatorClassName(), c1.getComparator()), classNameOrImpl(c2.getComparatorClassName(), c2.getComparator())));
        }
    }

    private static class RingbufferConfigChecker extends ConfigChecker<RingbufferConfig> {
        private static boolean isCompatible(RingbufferStoreConfig c1, RingbufferStoreConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(classNameOrImpl(c1.getClassName(), c1.getStoreImplementation()), classNameOrImpl(c2.getClassName(), c2.getStoreImplementation())) && nullSafeEqual(classNameOrImpl(c1.getFactoryClassName(), c1.getFactoryImplementation()), classNameOrImpl(c2.getFactoryClassName(), c2.getFactoryImplementation())) && nullSafeEqual(c1.getProperties(), c2.getProperties()));
        }

        @Override
        boolean check(RingbufferConfig c1, RingbufferConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getBackupCount(), c2.getBackupCount()) && nullSafeEqual(c1.getAsyncBackupCount(), c2.getAsyncBackupCount()) && nullSafeEqual(c1.getCapacity(), c2.getCapacity()) && nullSafeEqual(c1.getTimeToLiveSeconds(), c2.getTimeToLiveSeconds()) && nullSafeEqual(c1.getInMemoryFormat(), c2.getInMemoryFormat()) && nullSafeEqual(c1.getSplitBrainProtectionName(), c2.getSplitBrainProtectionName()) && isCompatible(c1.getRingbufferStoreConfig(), c2.getRingbufferStoreConfig()) && ConfigCompatibilityChecker.isCompatible(c1.getMergePolicyConfig(), c2.getMergePolicyConfig());
        }

        @Override
        RingbufferConfig getDefault(Config c) {
            return c.getRingbufferConfig("default");
        }
    }

    private static class QueueConfigChecker extends ConfigChecker<QueueConfig> {
        private static boolean isCompatible(QueueStoreConfig c1, QueueStoreConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(classNameOrImpl(c1.getClassName(), c1.getStoreImplementation()), classNameOrImpl(c2.getClassName(), c2.getStoreImplementation())) && nullSafeEqual(classNameOrImpl(c1.getFactoryClassName(), c1.getFactoryImplementation()), classNameOrImpl(c2.getFactoryClassName(), c2.getFactoryImplementation())) && nullSafeEqual(c1.getProperties(), c2.getProperties()));
        }

        @Override
        boolean check(QueueConfig c1, QueueConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getItemListenerConfigs(), c2.getItemListenerConfigs()) && nullSafeEqual(c1.getBackupCount(), c2.getBackupCount()) && nullSafeEqual(c1.getAsyncBackupCount(), c2.getAsyncBackupCount()) && nullSafeEqual(c1.getMaxSize(), c2.getMaxSize()) && nullSafeEqual(c1.getEmptyQueueTtl(), c2.getEmptyQueueTtl()) && isCompatible(c1.getQueueStoreConfig(), c2.getQueueStoreConfig()) && ConfigCompatibilityChecker.isCompatible(c1.getMergePolicyConfig(), c2.getMergePolicyConfig()) && nullSafeEqual(c1.isStatisticsEnabled(), c2.isStatisticsEnabled()) && nullSafeEqual(c1.getSplitBrainProtectionName(), c2.getSplitBrainProtectionName()) && nullSafeEqual(c1.getPriorityComparatorClassName(), c2.getPriorityComparatorClassName());
        }

        @Override
        QueueConfig getDefault(Config c) {
            return c.getQueueConfig("default");
        }
    }

    private static class ListConfigChecker extends ConfigChecker<ListConfig> {
        @Override
        boolean check(ListConfig c1, ListConfig c2) {
            return isCompatible(c1, c2);
        }

        @Override
        ListConfig getDefault(Config c) {
            return c.getListConfig("default");
        }
    }

    private static class SetConfigChecker extends ConfigChecker<SetConfig> {
        @Override
        boolean check(SetConfig c1, SetConfig c2) {
            return isCompatible(c1, c2);
        }

        @Override
        SetConfig getDefault(Config c) {
            return c.getSetConfig("default");
        }
    }

    private static class TopicConfigChecker extends ConfigChecker<TopicConfig> {
        @Override
        boolean check(TopicConfig c1, TopicConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.isGlobalOrderingEnabled(), c2.isGlobalOrderingEnabled()) && nullSafeEqual(c1.isStatisticsEnabled(), c2.isStatisticsEnabled()) && nullSafeEqual(c1.isMultiThreadingEnabled(), c2.isMultiThreadingEnabled()) && nullSafeEqual(c1.getMessageListenerConfigs(), c2.getMessageListenerConfigs());
        }

        @Override
        TopicConfig getDefault(Config c) {
            return c.getTopicConfig("default");
        }
    }

    private static class ReliableTopicConfigChecker extends ConfigChecker<ReliableTopicConfig> {
        @Override
        boolean check(ReliableTopicConfig c1, ReliableTopicConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getReadBatchSize(), c2.getReadBatchSize()) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.isStatisticsEnabled(), c2.isStatisticsEnabled()) && nullSafeEqual(c1.getMessageListenerConfigs(), c2.getMessageListenerConfigs()) && nullSafeEqual(c1.getTopicOverloadPolicy(), c2.getTopicOverloadPolicy());
        }

        @Override
        ReliableTopicConfig getDefault(Config c) {
            return c.getReliableTopicConfig("default");
        }
    }

    private static class ExecutorConfigChecker extends ConfigChecker<ExecutorConfig> {
        @Override
        boolean check(ExecutorConfig c1, ExecutorConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }
            int cap1 = c1.getQueueCapacity();
            int cap2 = c2.getQueueCapacity();
            return nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getPoolSize(), c2.getPoolSize()) && (nullSafeEqual(cap1, cap2) || (Math.min(cap1, cap2) == 0 && Math.max(cap1, cap2) == Integer.MAX_VALUE)) && nullSafeEqual(c1.getSplitBrainProtectionName(), c2.getSplitBrainProtectionName()) && nullSafeEqual(c1.isStatisticsEnabled(), c2.isStatisticsEnabled());
        }

        @Override
        ExecutorConfig getDefault(Config c) {
            return c.getExecutorConfig("default");
        }
    }

    private static class DurableExecutorConfigChecker extends ConfigChecker<DurableExecutorConfig> {
        @Override
        boolean check(DurableExecutorConfig c1, DurableExecutorConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getPoolSize(), c2.getPoolSize()) && nullSafeEqual(c1.getDurability(), c2.getDurability()) && nullSafeEqual(c1.getSplitBrainProtectionName(), c2.getSplitBrainProtectionName()) && nullSafeEqual(c1.getCapacity(), c2.getCapacity()) && nullSafeEqual(c1.isStatisticsEnabled(), c2.isStatisticsEnabled());
        }

        @Override
        DurableExecutorConfig getDefault(Config c) {
            return c.getDurableExecutorConfig("default");
        }
    }

    private static class ScheduledExecutorConfigChecker extends ConfigChecker<ScheduledExecutorConfig> {
        @Override
        boolean check(ScheduledExecutorConfig c1, ScheduledExecutorConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getDurability(), c2.getDurability()) && nullSafeEqual(c1.getSplitBrainProtectionName(), c2.getSplitBrainProtectionName()) && isCompatible(c1.getMergePolicyConfig(), c2.getMergePolicyConfig()) && nullSafeEqual(c1.getPoolSize(), c2.getPoolSize()) && nullSafeEqual(c1.isStatisticsEnabled(), c2.isStatisticsEnabled());
        }

        @Override
        ScheduledExecutorConfig getDefault(Config c) {
            return c.getScheduledExecutorConfig("default");
        }
    }

    private static class MultimapConfigChecker extends ConfigChecker<MultiMapConfig> {
        @Override
        boolean check(MultiMapConfig c1, MultiMapConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getValueCollectionType(), c2.getValueCollectionType()) && nullSafeEqual(c1.getEntryListenerConfigs(), c2.getEntryListenerConfigs()) && nullSafeEqual(c1.isBinary(), c2.isBinary()) && nullSafeEqual(c1.getBackupCount(), c2.getBackupCount()) && nullSafeEqual(c1.getAsyncBackupCount(), c2.getAsyncBackupCount()) && nullSafeEqual(c1.isStatisticsEnabled(), c2.isStatisticsEnabled()) && nullSafeEqual(c1.getSplitBrainProtectionName(), c2.getSplitBrainProtectionName()) && ConfigCompatibilityChecker.isCompatible(c1.getMergePolicyConfig(), c2.getMergePolicyConfig());
        }

        @Override
        MultiMapConfig getDefault(Config c) {
            return c.getMultiMapConfig("default");
        }
    }

    private static class ReplicatedMapConfigChecker extends ConfigChecker<ReplicatedMapConfig> {
        @Override
        boolean check(ReplicatedMapConfig c1, ReplicatedMapConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getInMemoryFormat(), c2.getInMemoryFormat()) && nullSafeEqual(c1.isAsyncFillup(), c2.isAsyncFillup()) && nullSafeEqual(c1.isStatisticsEnabled(), c2.isStatisticsEnabled()) && nullSafeEqual(c1.getSplitBrainProtectionName(), c2.getSplitBrainProtectionName()) && ConfigCompatibilityChecker.isCompatible(c1.getMergePolicyConfig(), c2.getMergePolicyConfig()) && isCollectionCompatible(c1.getListenerConfigs(), c2.getListenerConfigs(), new ReplicatedMapListenerConfigChecker());
        }

        @Override
        ReplicatedMapConfig getDefault(Config c) {
            return c.getReplicatedMapConfig("default");
        }
    }

    private static class ReplicatedMapListenerConfigChecker extends ConfigChecker<ListenerConfig> {
        @Override
        boolean check(ListenerConfig c1, ListenerConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(classNameOrImpl(c1.getClassName(), c1.getImplementation()), classNameOrImpl(c2.getClassName(), c2.getImplementation()));
        }
    }

    private static class CardinalityEstimatorConfigChecker extends ConfigChecker<CardinalityEstimatorConfig> {
        @Override
        boolean check(CardinalityEstimatorConfig c1, CardinalityEstimatorConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }
            return nullSafeEqual(c1.getName(), c2.getName()) && c1.getBackupCount() == c2.getBackupCount() && c1.getAsyncBackupCount() == c2.getAsyncBackupCount() && c1.getAsyncBackupCount() == c2.getAsyncBackupCount() && isCompatible(c1.getMergePolicyConfig(), c2.getMergePolicyConfig()) && nullSafeEqual(c1.getSplitBrainProtectionName(), c2.getSplitBrainProtectionName());
        }

        @Override
        CardinalityEstimatorConfig getDefault(Config c) {
            return c.getCardinalityEstimatorConfig("default");
        }
    }

    private static class FlakeIdGeneratorConfigChecker extends ConfigChecker<FlakeIdGeneratorConfig> {
        @Override
        boolean check(FlakeIdGeneratorConfig c1, FlakeIdGeneratorConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }
            return nullSafeEqual(c1.getName(), c2.getName()) && c1.getPrefetchCount() == c2.getPrefetchCount() && c1.getPrefetchValidityMillis() == c2.getPrefetchValidityMillis() && c1.getEpochStart() == c2.getEpochStart() && c1.getNodeIdOffset() == c2.getNodeIdOffset() && c1.getBitsSequence() == c2.getBitsSequence() && c1.getBitsNodeId() == c2.getBitsNodeId() && c1.getAllowedFutureMillis() == c2.getAllowedFutureMillis() && c1.isStatisticsEnabled() == c2.isStatisticsEnabled();
        }

        @Override
        FlakeIdGeneratorConfig getDefault(Config c) {
            return c.getFlakeIdGeneratorConfig("default");
        }
    }

    private static class PNCounterConfigChecker extends ConfigChecker<PNCounterConfig> {
        @Override
        boolean check(PNCounterConfig c1, PNCounterConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }
            return nullSafeEqual(c1.getName(), c2.getName()) && c1.getReplicaCount() == c2.getReplicaCount() && nullSafeEqual(c1.getSplitBrainProtectionName(), c2.getSplitBrainProtectionName());
        }

        @Override
        PNCounterConfig getDefault(Config c) {
            return c.getPNCounterConfig("default");
        }
    }

    private static class DataConnectionConfigChecker extends ConfigChecker<DataConnectionConfig> {
        @Override
        boolean check(DataConnectionConfig c1, DataConnectionConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }
            return nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getType(), c2.getType()) && c1.isShared() == c2.isShared() && nullSafeEqual(c1.getProperties(), c2.getProperties());
        }

        @Override
        DataConnectionConfig getDefault(Config c) {
            return c.getDataConnectionConfig("default");
        }
    }

    private static class TpcConfigChecker extends ConfigChecker<TpcConfig> {
        @Override
        boolean check(TpcConfig c1, TpcConfig c2) {
            return nullSafeEqual(c1, c2);
        }
    }

    private static class UserCodeNamespacesConfigChecker extends ConfigChecker<UserCodeNamespacesConfig> {
        @Override
        boolean check(UserCodeNamespacesConfig c1, UserCodeNamespacesConfig c2) {
            return nullSafeEqual(c1, c2);
        }
    }

    public static class CPSubsystemConfigChecker extends ConfigChecker<CPSubsystemConfig> {

        @Override
        boolean check(CPSubsystemConfig c1, CPSubsystemConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }

            boolean cpSubsystemConfigValuesEqual = (c1.getCPMemberCount() == c2.getCPMemberCount() && c1.getGroupSize() == c2.getGroupSize() && c1.getSessionTimeToLiveSeconds() == c2.getSessionTimeToLiveSeconds() && c1.getSessionHeartbeatIntervalSeconds() == c2.getSessionHeartbeatIntervalSeconds() && c1.getMissingCPMemberAutoRemovalSeconds() == c2.getMissingCPMemberAutoRemovalSeconds() && c1.isFailOnIndeterminateOperationState() == c2.isFailOnIndeterminateOperationState()) && c1.isPersistenceEnabled() == c2.isPersistenceEnabled() && c1.getBaseDir().getAbsoluteFile().equals(c2.getBaseDir().getAbsoluteFile()) && c1.getDataLoadTimeoutSeconds() == c2.getDataLoadTimeoutSeconds() && c1.getCPMemberPriority() == c2.getCPMemberPriority() && c1.getCPMapLimit() == c2.getCPMapLimit();

            if (!cpSubsystemConfigValuesEqual) {
                return false;
            }

            RaftAlgorithmConfig r1 = c1.getRaftAlgorithmConfig();
            RaftAlgorithmConfig r2 = c2.getRaftAlgorithmConfig();

            final boolean raftAlgorithmConfigEqual = (r1.getLeaderElectionTimeoutInMillis() == r2.getLeaderElectionTimeoutInMillis() && r1.getLeaderHeartbeatPeriodInMillis() == r2.getLeaderHeartbeatPeriodInMillis() && r1.getAppendRequestMaxEntryCount() == r2.getAppendRequestMaxEntryCount() && r1.getMaxMissedLeaderHeartbeatCount() == r2.getMaxMissedLeaderHeartbeatCount() && r1.getCommitIndexAdvanceCountToSnapshot() == r2.getCommitIndexAdvanceCountToSnapshot() && r1.getAppendRequestBackoffTimeoutInMillis() == r2.getAppendRequestBackoffTimeoutInMillis() && r1.getUncommittedEntryCountToRejectNewAppends() == r2.getUncommittedEntryCountToRejectNewAppends());

            if (!raftAlgorithmConfigEqual) {
                return false;
            }

            Map<String, SemaphoreConfig> semaphores1 = c1.getSemaphoreConfigs();

            if (semaphores1.size() != c2.getSemaphoreConfigs().size()) {
                return false;
            }

            for (Entry<String, SemaphoreConfig> e : semaphores1.entrySet()) {
                SemaphoreConfig s2 = c2.findSemaphoreConfig(e.getKey());
                if (s2 == null) {
                    return false;
                }
                if (e.getValue().isJDKCompatible() != s2.isJDKCompatible()) {
                    return false;
                }

                if (e.getValue().getInitialPermits() != s2.getInitialPermits()) {
                    return false;
                }
            }

            Map<String, FencedLockConfig> locks1 = c1.getLockConfigs();

            if (locks1.size() != c2.getLockConfigs().size()) {
                return false;
            }

            for (Entry<String, FencedLockConfig> e : locks1.entrySet()) {
                FencedLockConfig s2 = c2.findLockConfig(e.getKey());
                if (s2 == null) {
                    return false;
                }
                if (e.getValue().getLockAcquireLimit() != s2.getLockAcquireLimit()) {
                    return false;
                }
            }

            Map<String, CPMapConfig> maps1 = c1.getCpMapConfigs();

            if (maps1.size() != c2.getCpMapConfigs().size()) {
                return false;
            }

            for (Entry<String, CPMapConfig> e : maps1.entrySet()) {
                CPMapConfig c2CPMapConfig = c2.findCPMapConfig(e.getKey());
                if (c2CPMapConfig == null) {
                    return false;
                }
                if (e.getValue().getMaxSizeMb() != c2CPMapConfig.getMaxSizeMb()) {
                    return false;
                }
            }

            return true;
        }

        @Override
        CPSubsystemConfig getDefault(Config c) {
            return c.getCPSubsystemConfig();
        }
    }

    public static class MetricsConfigChecker extends ConfigChecker<MetricsConfig> {

        @Override
        boolean check(MetricsConfig c1, MetricsConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }

            return c1.isEnabled() == c2.isEnabled() && c1.getManagementCenterConfig().isEnabled() == c2.getManagementCenterConfig().isEnabled() && c1.getManagementCenterConfig().getRetentionSeconds() == c2.getManagementCenterConfig().getRetentionSeconds() && c1.getJmxConfig().isEnabled() == c2.getJmxConfig().isEnabled() && c1.getCollectionFrequencySeconds() == c2.getCollectionFrequencySeconds();
        }

        @Override
        MetricsConfig getDefault(Config c) {
            return c.getMetricsConfig();
        }
    }

    public static class NativeMemoryConfigChecker extends ConfigChecker<NativeMemoryConfig> {

        @Override
        boolean check(NativeMemoryConfig c1, NativeMemoryConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }

            return c1.isEnabled() == c2.isEnabled() && c1.getAllocatorType() == c2.getAllocatorType() && c1.getMetadataSpacePercentage() == c2.getMetadataSpacePercentage() && c1.getMinBlockSize() == c2.getMinBlockSize() && c1.getPageSize() == c2.getPageSize() && c1.getPersistentMemoryConfig().isEnabled() == c2.getPersistentMemoryConfig().isEnabled() && c1.getPersistentMemoryConfig().getMode() == c2.getPersistentMemoryConfig().getMode() && c1.getPersistentMemoryConfig().getDirectoryConfigs().equals(c2.getPersistentMemoryConfig().getDirectoryConfigs());
        }

        @Override
        NativeMemoryConfig getDefault(Config c) {
            return c.getNativeMemoryConfig();
        }
    }

    public static class SqlConfigChecker extends ConfigChecker<SqlConfig> {

        @Override
        boolean check(SqlConfig c1, SqlConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }

            return c1.getStatementTimeoutMillis() == c2.getStatementTimeoutMillis() && c1.isCatalogPersistenceEnabled() == c2.isCatalogPersistenceEnabled() && nullSafeEqual(c1.getJavaReflectionFilterConfig(), c2.getJavaReflectionFilterConfig());
        }

        @Override
        SqlConfig getDefault(Config c) {
            return c.getSqlConfig();
        }
    }

    public static class InstanceTrackingConfigChecker extends ConfigChecker<InstanceTrackingConfig> {

        @Override
        boolean check(InstanceTrackingConfig c1, InstanceTrackingConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }

            return c1.isEnabled() == c2.isEnabled() && nullSafeEqual(c1.getFileName(), c2.getFileName()) && nullSafeEqual(c1.getFormatPattern(), c2.getFormatPattern());
        }

        @Override
        InstanceTrackingConfig getDefault(Config c) {
            return c.getInstanceTrackingConfig();
        }
    }

    private static class CacheSimpleConfigChecker extends ConfigChecker<CacheSimpleConfig> {
        private static boolean isCompatible(ExpiryPolicyFactoryConfig c1, ExpiryPolicyFactoryConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getClassName(), c2.getClassName()) && isCompatible(c1.getTimedExpiryPolicyFactoryConfig(), c2.getTimedExpiryPolicyFactoryConfig());
        }

        private static boolean isCompatible(TimedExpiryPolicyFactoryConfig c1, TimedExpiryPolicyFactoryConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getExpiryPolicyType(), c2.getExpiryPolicyType()) && isCompatible(c1.getDurationConfig(), c2.getDurationConfig());
        }

        private static boolean isCompatible(DurationConfig c1, DurationConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getDurationAmount(), c2.getDurationAmount()) && nullSafeEqual(c1.getTimeUnit(), c2.getTimeUnit());
        }

        private static boolean isCompatible(EvictionConfig c1, EvictionConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getSize(), c2.getSize()) && nullSafeEqual(c1.getMaxSizePolicy(), c2.getMaxSizePolicy()) && nullSafeEqual(c1.getEvictionPolicy(), c2.getEvictionPolicy()) && nullSafeEqual(c1.getComparatorClassName(), c2.getComparatorClassName());
        }

        private static boolean isCompatible(WanReplicationRef c1, WanReplicationRef c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getMergePolicyClassName(), c2.getMergePolicyClassName()) && nullSafeEqual(c1.getFilters(), c2.getFilters()) && nullSafeEqual(c1.isRepublishingEnabled(), c2.isRepublishingEnabled());
        }

        @Override
        boolean check(CacheSimpleConfig c1, CacheSimpleConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getKeyType(), c2.getKeyType()) && nullSafeEqual(c1.getValueType(), c2.getValueType()) && nullSafeEqual(c1.isStatisticsEnabled(), c2.isStatisticsEnabled()) && nullSafeEqual(c1.isManagementEnabled(), c2.isManagementEnabled()) && nullSafeEqual(c1.isReadThrough(), c2.isReadThrough()) && nullSafeEqual(c1.isWriteThrough(), c2.isWriteThrough()) && nullSafeEqual(c1.getCacheLoaderFactory(), c2.getCacheLoaderFactory()) && nullSafeEqual(c1.getCacheWriterFactory(), c2.getCacheWriterFactory()) && nullSafeEqual(c1.getCacheLoader(), c2.getCacheLoader()) && nullSafeEqual(c1.getCacheWriter(), c2.getCacheWriter()) && isCompatible(c1.getExpiryPolicyFactoryConfig(), c2.getExpiryPolicyFactoryConfig()) && isCollectionCompatible(c1.getCacheEntryListeners(), c2.getCacheEntryListeners(), new CacheSimpleEntryListenerConfigChecker()) && nullSafeEqual(c1.getAsyncBackupCount(), c2.getAsyncBackupCount()) && nullSafeEqual(c1.getBackupCount(), c2.getBackupCount()) && nullSafeEqual(c1.getInMemoryFormat(), c2.getInMemoryFormat()) && isCompatible(c1.getEvictionConfig(), c2.getEvictionConfig()) && isCompatible(c1.getWanReplicationRef(), c2.getWanReplicationRef()) && nullSafeEqual(c1.getSplitBrainProtectionName(), c2.getSplitBrainProtectionName()) && nullSafeEqual(c1.getPartitionLostListenerConfigs(), c2.getPartitionLostListenerConfigs()) && nullSafeEqual(c1.getMergePolicyConfig(), c2.getMergePolicyConfig()) && ConfigCompatibilityChecker.isCompatible(c1.getHotRestartConfig(), c2.getHotRestartConfig()) && ConfigCompatibilityChecker.isCompatible(c1.getEventJournalConfig(), c2.getEventJournalConfig());
        }

        @Override
        CacheSimpleConfig getDefault(Config c) {
            return c.getCacheConfig("default");
        }
    }

    public static class MapConfigChecker extends ConfigChecker<MapConfig> {
        private static boolean isCompatible(WanReplicationRef c1, WanReplicationRef c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getMergePolicyClassName(), c2.getMergePolicyClassName()) && nullSafeEqual(c1.getFilters(), c2.getFilters()) && nullSafeEqual(c1.isRepublishingEnabled(), c2.isRepublishingEnabled());
        }

        private static boolean isCompatible(NearCacheConfig c1, NearCacheConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getTimeToLiveSeconds(), c2.getTimeToLiveSeconds()) && isCompatible(c1.getEvictionConfig(), c2.getEvictionConfig());
        }

        private static boolean isCompatible(EvictionConfig c1, EvictionConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getSize(), c2.getSize()) && nullSafeEqual(c1.getMaxSizePolicy(), c2.getMaxSizePolicy()) && nullSafeEqual(c1.getEvictionPolicy(), c2.getEvictionPolicy()) && nullSafeEqual(c1.getEvictionStrategyType(), c2.getEvictionStrategyType()) && nullSafeEqual(c1.getComparatorClassName(), c2.getComparatorClassName());
        }

        private static boolean isCompatible(MapStoreConfig c1, MapStoreConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(classNameOrImpl(c1.getClassName(), c1.getImplementation()), classNameOrImpl(c2.getClassName(), c2.getImplementation())) && nullSafeEqual(classNameOrImpl(c1.getFactoryClassName(), c1.getFactoryImplementation()), classNameOrImpl(c2.getFactoryClassName(), c2.getFactoryImplementation())) && nullSafeEqual(c1.getProperties(), c2.getProperties())) && nullSafeEqual(c1.isOffload(), c2.isOffload()) && nullSafeEqual(c1.isWriteCoalescing(), c2.isWriteCoalescing()) && nullSafeEqual(c1.getWriteBatchSize(), c2.getWriteBatchSize()) && nullSafeEqual(c1.getWriteDelaySeconds(), c2.getWriteDelaySeconds()) && nullSafeEqual(c1.getInitialLoadMode(), c2.getInitialLoadMode());
        }

        @Override
        public boolean check(MapConfig c1, MapConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }
            int maxSize1 = c1.getEvictionConfig().getSize();
            int maxSize2 = c2.getEvictionConfig().getSize();

            return nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getInMemoryFormat(), c2.getInMemoryFormat()) && nullSafeEqual(c1.getMetadataPolicy(), c2.getMetadataPolicy()) && nullSafeEqual(c1.isStatisticsEnabled(), c2.isStatisticsEnabled()) && nullSafeEqual(c1.getCacheDeserializedValues(), c2.getCacheDeserializedValues()) && nullSafeEqual(c1.getBackupCount(), c2.getBackupCount()) && nullSafeEqual(c1.getAsyncBackupCount(), c2.getAsyncBackupCount()) && nullSafeEqual(c1.getTimeToLiveSeconds(), c2.getTimeToLiveSeconds()) && nullSafeEqual(c1.getMaxIdleSeconds(), c2.getMaxIdleSeconds()) && nullSafeEqual(c1.getEvictionConfig().getEvictionPolicy(), c2.getEvictionConfig().getEvictionPolicy()) && (nullSafeEqual(maxSize1, maxSize2) || (Math.min(maxSize1, maxSize2) == 0 && Math.max(maxSize1, maxSize2) == Integer.MAX_VALUE)) && ConfigCompatibilityChecker.isCompatible(c1.getMergePolicyConfig(), c2.getMergePolicyConfig()) && nullSafeEqual(c1.isReadBackupData(), c2.isReadBackupData()) && ConfigCompatibilityChecker.isCompatible(c1.getMerkleTreeConfig(), c2.getMerkleTreeConfig()) && ConfigCompatibilityChecker.isCompatible(c1.getHotRestartConfig(), c2.getHotRestartConfig()) && ConfigCompatibilityChecker.isCompatible(c1.getEventJournalConfig(), c2.getEventJournalConfig()) && isCompatible(c1.getMapStoreConfig(), c2.getMapStoreConfig()) && isCompatible(c1.getNearCacheConfig(), c2.getNearCacheConfig()) && isCompatible(c1.getWanReplicationRef(), c2.getWanReplicationRef()) && isCollectionCompatible(c1.getIndexConfigs(), c2.getIndexConfigs(), new IndexConfigChecker()) && isCollectionCompatible(c1.getAttributeConfigs(), c2.getAttributeConfigs(), new AttributeConfigChecker()) && isCollectionCompatible(c1.getEntryListenerConfigs(), c2.getEntryListenerConfigs(), new EntryListenerConfigChecker()) && nullSafeEqual(c1.getPartitionLostListenerConfigs(), c2.getPartitionLostListenerConfigs()) && nullSafeEqual(c1.getSplitBrainProtectionName(), c2.getSplitBrainProtectionName()) && nullSafeEqual(c1.getPartitioningStrategyConfig(), c2.getPartitioningStrategyConfig()) && nullSafeEqual(c1.getTieredStoreConfig(), c2.getTieredStoreConfig()) && isCollectionCompatible(c1.getPartitioningAttributeConfigs(), c2.getPartitioningAttributeConfigs(), new PartitioningAttributesConfigChecker());
        }

        @Override
        MapConfig getDefault(Config c) {
            return c.getMapConfig("default");
        }
    }

    private static class IndexConfigChecker extends ConfigChecker<IndexConfig> {
        @Override
        boolean check(IndexConfig c1, IndexConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1, c2);
        }
    }

    private static class DynamicConfigurationConfigChecker extends ConfigChecker<DynamicConfigurationConfig> {
        @Override
        boolean check(DynamicConfigurationConfig t1, DynamicConfigurationConfig t2) {
            return Objects.equals(t1, t2);
        }
    }

    private static class DeviceConfigChecker extends ConfigChecker<DeviceConfig> {
        @Override
        boolean check(DeviceConfig t1, DeviceConfig t2) {
            return Objects.equals(t1, t2);
        }
    }

    private static class CacheSimpleEntryListenerConfigChecker extends ConfigChecker<CacheSimpleEntryListenerConfig> {
        @Override
        boolean check(CacheSimpleEntryListenerConfig c1, CacheSimpleEntryListenerConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getCacheEntryListenerFactory(), c2.getCacheEntryListenerFactory()) && nullSafeEqual(c1.getCacheEntryEventFilterFactory(), c2.getCacheEntryEventFilterFactory()) && nullSafeEqual(c1.isOldValueRequired(), c2.isOldValueRequired()) && nullSafeEqual(c1.isSynchronous(), c2.isSynchronous());
        }
    }

    private static class EntryListenerConfigChecker extends ConfigChecker<EntryListenerConfig> {
        @Override
        boolean check(EntryListenerConfig c1, EntryListenerConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.isLocal(), c2.isLocal()) && nullSafeEqual(c1.isIncludeValue(), c2.isIncludeValue()) && nullSafeEqual(classNameOrImpl(c1.getClassName(), c1.getImplementation()), classNameOrImpl(c2.getClassName(), c2.getImplementation()));
        }
    }

    private static class AttributeConfigChecker extends ConfigChecker<AttributeConfig> {
        @Override
        boolean check(AttributeConfig c1, AttributeConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getExtractorClassName(), c2.getExtractorClassName());
        }
    }

    public static class DiscoveryStrategyConfigChecker extends ConfigChecker<DiscoveryStrategyConfig> {
        @Override
        public boolean check(DiscoveryStrategyConfig c1, DiscoveryStrategyConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && discoveryStrategyClassNameEqual(c1, c2) && nullSafeEqual(c1.getProperties(), c2.getProperties());
        }

        private boolean discoveryStrategyClassNameEqual(DiscoveryStrategyConfig c1, DiscoveryStrategyConfig c2) {
            return nullSafeEqual(classNameOrImpl(c1.getClassName(), c1.getDiscoveryStrategyFactory()), classNameOrImpl(c2.getClassName(), c2.getDiscoveryStrategyFactory()));
        }
    }

    private static class MemberGroupConfigChecker extends ConfigChecker<MemberGroupConfig> {
        @Override
        boolean check(MemberGroupConfig c1, MemberGroupConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(new ArrayList<>(c1.getInterfaces()), new ArrayList<>(c2.getInterfaces()));
        }
    }

    private static class SerializerConfigChecker extends ConfigChecker<SerializerConfig> {
        @Override
        boolean check(SerializerConfig c1, SerializerConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && nullSafeEqual(classNameOrImpl(c1.getClassName(), c1.getImplementation()), classNameOrImpl(c2.getClassName(), c2.getImplementation())) && nullSafeEqual(classNameOrClass(c1.getTypeClassName(), c1.getTypeClass()), classNameOrClass(c2.getTypeClassName(), c2.getTypeClass())));
        }
    }

    public static class WanSyncConfigChecker extends ConfigChecker<WanSyncConfig> {
        @Override
        public boolean check(WanSyncConfig c1, WanSyncConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getConsistencyCheckStrategy(), c2.getConsistencyCheckStrategy());
        }
    }

    private static class NetworkConfigChecker extends ConfigChecker<NetworkConfig> {
        private static final JoinConfigChecker JOIN_CONFIG_CHECKER = new JoinConfigChecker();
        private static final IcmpFailureDetectorConfigChecker FAILURE_DETECTOR_CONFIG_CHECKER = new IcmpFailureDetectorConfigChecker();
        private static final MemberAddressProviderConfigChecker MEMBER_ADDRESS_PROVIDER_CONFIG_CHECKER = new MemberAddressProviderConfigChecker();
        private static final OutboundPortDefinitionsChecker OUTBOUND_PORT_DEFINITIONS_CHECKER = new OutboundPortDefinitionsChecker();
        private static final InterfacesConfigChecker INTERFACES_CONFIG_CHECKER = new InterfacesConfigChecker();
        private static final SSLConfigChecker SSL_CONFIG_CHECKER = new SSLConfigChecker();
        private static final SocketInterceptorConfigChecker SOCKET_INTERCEPTOR_CONFIG_CHECKER = new SocketInterceptorConfigChecker();
        private static final SymmetricEncryptionConfigChecker SYMMETRIC_ENCRYPTION_CONFIG_CHECKER = new SymmetricEncryptionConfigChecker();
        private static final RestApiConfigChecker REST_API_CONFIG_CHECKER = new RestApiConfigChecker();
        private static final MemcacheProtocolConfigChecker MEMCACHE_PROTOCOL_CONFIG_CHECKER = new MemcacheProtocolConfigChecker();

        @Override
        boolean check(NetworkConfig c1, NetworkConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getPort(), c2.getPort()) && nullSafeEqual(c1.getPortCount(), c2.getPortCount()) && nullSafeEqual(c1.isPortAutoIncrement(), c2.isPortAutoIncrement()) && nullSafeEqual(c1.isReuseAddress(), c2.isReuseAddress()) && nullSafeEqual(c1.getPublicAddress(), c2.getPublicAddress()) && OUTBOUND_PORT_DEFINITIONS_CHECKER.check(c1.getOutboundPortDefinitions(), c2.getOutboundPortDefinitions()) && nullSafeEqual(c1.getOutboundPorts(), c2.getOutboundPorts()) && INTERFACES_CONFIG_CHECKER.check(c1.getInterfaces(), c2.getInterfaces()) && JOIN_CONFIG_CHECKER.check(c1.getJoin(), c2.getJoin()) && FAILURE_DETECTOR_CONFIG_CHECKER.check(c1.getIcmpFailureDetectorConfig(), c2.getIcmpFailureDetectorConfig()) && MEMBER_ADDRESS_PROVIDER_CONFIG_CHECKER.check(c1.getMemberAddressProviderConfig(), c2.getMemberAddressProviderConfig()) && SYMMETRIC_ENCRYPTION_CONFIG_CHECKER.check(c1.getSymmetricEncryptionConfig(), c2.getSymmetricEncryptionConfig()) && SOCKET_INTERCEPTOR_CONFIG_CHECKER.check(c1.getSocketInterceptorConfig(), c2.getSocketInterceptorConfig()) && SSL_CONFIG_CHECKER.check(c1.getSSLConfig(), c2.getSSLConfig()) && REST_API_CONFIG_CHECKER.check(c1.getRestApiConfig(), c2.getRestApiConfig()) && MEMCACHE_PROTOCOL_CONFIG_CHECKER.check(c1.getMemcacheProtocolConfig(), c2.getMemcacheProtocolConfig()) && nullSafeEqual(c1.getTpcSocketConfig(), c2.getTpcSocketConfig());
        }
    }

    private static class AdvancedNetworkConfigChecker extends ConfigChecker<AdvancedNetworkConfig> {

        private static final JoinConfigChecker JOIN_CONFIG_CHECKER = new JoinConfigChecker();
        private static final IcmpFailureDetectorConfigChecker FAILURE_DETECTOR_CONFIG_CHECKER = new IcmpFailureDetectorConfigChecker();
        private static final MemberAddressProviderConfigChecker MEMBER_ADDRESS_PROVIDER_CONFIG_CHECKER = new MemberAddressProviderConfigChecker();
        private static final EndpointConfigChecker ENDPOINT_CONFIG_CHECKER = new EndpointConfigChecker();

        @Override
        boolean check(AdvancedNetworkConfig t1, AdvancedNetworkConfig t2) {
            boolean t1Disabled = t1 == null || !t1.isEnabled();
            boolean t2Disabled = t1 == null || !t2.isEnabled();
            if (t1Disabled && t2Disabled) {
                return true;
            }
            boolean compatible = JOIN_CONFIG_CHECKER.check(t1.getJoin(), t2.getJoin()) && FAILURE_DETECTOR_CONFIG_CHECKER.check(t1.getIcmpFailureDetectorConfig(), t2.getIcmpFailureDetectorConfig()) && MEMBER_ADDRESS_PROVIDER_CONFIG_CHECKER.check(t1.getMemberAddressProviderConfig(), t2.getMemberAddressProviderConfig());

            if (!compatible || (t1.getEndpointConfigs().size() != t2.getEndpointConfigs().size())) {
                return false;
            }
            Set<EndpointQualifier> t1EndpointQualifiers = t1.getEndpointConfigs().keySet();
            Set<EndpointQualifier> t2EndpointQualifiers = t2.getEndpointConfigs().keySet();
            isCollectionCompatible(t1EndpointQualifiers, t2EndpointQualifiers, new ConfigChecker<>() {
                @Override
                boolean check(EndpointQualifier t1, EndpointQualifier t2) {
                    return t1.equals(t2);
                }
            });

            boolean endpointConfigsCompatible = true;
            for (EndpointQualifier endpointQualifier : t1EndpointQualifiers) {
                EndpointConfig t1Config = t1.getEndpointConfigs().get(endpointQualifier);
                EndpointConfig t2Config = t2.getEndpointConfigs().get(endpointQualifier);
                endpointConfigsCompatible = endpointConfigsCompatible && (t1Config.getClass().equals(t2Config.getClass())) && ENDPOINT_CONFIG_CHECKER.check(t1Config, t2Config);
            }
            return endpointConfigsCompatible;
        }
    }

    private static class JoinConfigChecker extends ConfigChecker<JoinConfig> {
        private static final AliasedDiscoveryConfigsChecker ALIASED_DISCOVERY_CONFIGS_CHECKER = new AliasedDiscoveryConfigsChecker();
        private static final DiscoveryConfigChecker DISCOVERY_CONFIG_CHECKER = new DiscoveryConfigChecker();

        private static boolean isCompatible(JoinConfig c1, JoinConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && isCompatible(c1.getMulticastConfig(), c2.getMulticastConfig()) && isCompatible(c1.getTcpIpConfig(), c2.getTcpIpConfig()) && ALIASED_DISCOVERY_CONFIGS_CHECKER.check(AliasedDiscoveryConfigUtils.aliasedDiscoveryConfigsFrom(c1), AliasedDiscoveryConfigUtils.aliasedDiscoveryConfigsFrom(c2)) && DISCOVERY_CONFIG_CHECKER.check(c1.getDiscoveryConfig(), c2.getDiscoveryConfig());
        }

        private static boolean isCompatible(TcpIpConfig c1, TcpIpConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(c1.getConnectionTimeoutSeconds(), c2.getConnectionTimeoutSeconds()) && nullSafeEqual(c1.getMembers(), c2.getMembers())) && nullSafeEqual(c1.getRequiredMember(), c2.getRequiredMember());
        }

        private static boolean isCompatible(MulticastConfig c1, MulticastConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(c1.getMulticastGroup(), c2.getMulticastGroup()) && nullSafeEqual(c1.getMulticastPort(), c2.getMulticastPort())) && nullSafeEqual(c1.getMulticastTimeoutSeconds(), c2.getMulticastTimeoutSeconds()) && nullSafeEqual(c1.getMulticastTimeToLive(), c2.getMulticastTimeToLive()) && nullSafeEqual(c1.getTrustedInterfaces(), c2.getTrustedInterfaces());
        }

        @Override
        boolean check(JoinConfig t1, JoinConfig t2) {
            return isCompatible(t1, t2);
        }
    }

    private static class IcmpFailureDetectorConfigChecker extends ConfigChecker<IcmpFailureDetectorConfig> {
        @Override
        boolean check(IcmpFailureDetectorConfig t1, IcmpFailureDetectorConfig t2) {
            boolean t1Disabled = t1 == null || !t1.isEnabled();
            boolean t2Disabled = t2 == null || !t2.isEnabled();
            return t1 == t2 || (t1Disabled && t2Disabled) || (t1 != null && t2 != null && t1.isParallelMode() == t2.isParallelMode() && t1.isFailFastOnStartup() == t2.isFailFastOnStartup() && t1.getMaxAttempts() == t2.getMaxAttempts() && t1.getIntervalMilliseconds() == t2.getIntervalMilliseconds() && t1.getTimeoutMilliseconds() == t2.getTimeoutMilliseconds() && t1.getTtl() == t2.getTtl());
        }
    }

    private static class MemberAddressProviderConfigChecker extends ConfigChecker<MemberAddressProviderConfig> {

        @Override
        boolean check(MemberAddressProviderConfig t1, MemberAddressProviderConfig t2) {
            boolean t1Disabled = t1 == null || !t1.isEnabled();
            boolean t2Disabled = t2 == null || !t2.isEnabled();
            return t1 == t2 || (t1Disabled && t2Disabled) || (t1 != null && t2 != null && nullSafeEqual(classNameOrImpl(t1.getClassName(), t1.getImplementation()), classNameOrImpl(t2.getClassName(), t2.getImplementation())));
        }
    }

    private static class EndpointConfigChecker extends ConfigChecker<EndpointConfig> {

        private static final OutboundPortDefinitionsChecker OUTBOUND_PORT_DEFINITIONS_CHECKER = new OutboundPortDefinitionsChecker();
        private static final InterfacesConfigChecker INTERFACES_CONFIG_CHECKER = new InterfacesConfigChecker();
        private static final SSLConfigChecker SSL_CONFIG_CHECKER = new SSLConfigChecker();
        private static final SocketInterceptorConfigChecker SOCKET_INTERCEPTOR_CONFIG_CHECKER = new SocketInterceptorConfigChecker();
        private static final SymmetricEncryptionConfigChecker SYMMETRIC_ENCRYPTION_CONFIG_CHECKER = new SymmetricEncryptionConfigChecker();

        @Override
        boolean check(EndpointConfig c1, EndpointConfig c2) {
            if (c1.getClass() != c2.getClass()) {
                return false;
            }
            boolean compatible = c1 == c2 || !(c1 == null || c2 == null) && OUTBOUND_PORT_DEFINITIONS_CHECKER.check(c1.getOutboundPortDefinitions(), c2.getOutboundPortDefinitions()) && nullSafeEqual(c1.getOutboundPorts(), c2.getOutboundPorts()) && INTERFACES_CONFIG_CHECKER.check(c1.getInterfaces(), c2.getInterfaces()) && SSL_CONFIG_CHECKER.check(c1.getSSLConfig(), c2.getSSLConfig()) && SOCKET_INTERCEPTOR_CONFIG_CHECKER.check(c1.getSocketInterceptorConfig(), c2.getSocketInterceptorConfig()) && SYMMETRIC_ENCRYPTION_CONFIG_CHECKER.check(c1.getSymmetricEncryptionConfig(), c2.getSymmetricEncryptionConfig()) && (c1.isSocketBufferDirect() == c2.isSocketBufferDirect()) && (c1.isSocketKeepAlive() == c2.isSocketKeepAlive()) && (c1.isSocketBufferDirect() == c2.isSocketBufferDirect()) && (c1.getSocketConnectTimeoutSeconds() == c2.getSocketConnectTimeoutSeconds()) && (c1.getSocketLingerSeconds() == c2.getSocketLingerSeconds()) && (c1.getSocketRcvBufferSizeKb() == c2.getSocketRcvBufferSizeKb()) && (c1.getSocketSendBufferSizeKb() == c2.getSocketSendBufferSizeKb()) && (c1.getSocketKeepIntervalSeconds() == c2.getSocketKeepIntervalSeconds()) && (c1.getSocketKeepIdleSeconds() == c2.getSocketKeepIdleSeconds()) && (c1.getSocketKeepCount() == c2.getSocketKeepCount()) && nullSafeEqual(c1.getTpcSocketConfig(), c2.getTpcSocketConfig());

            if (c1 instanceof ServerSocketEndpointConfig s1) {
                ServerSocketEndpointConfig s2 = (ServerSocketEndpointConfig) c2;
                return compatible && nullSafeEqual(s1.getPort(), s2.getPort()) && nullSafeEqual(s1.getPortCount(), s2.getPortCount()) && (s1.isPortAutoIncrement() == s2.isPortAutoIncrement()) && nullSafeEqual(s1.isReuseAddress(), s2.isReuseAddress()) && nullSafeEqual(s1.getPublicAddress(), s2.getPublicAddress());
            } else {
                return compatible;
            }
        }
    }

    private static class OutboundPortDefinitionsChecker extends ConfigChecker<Collection<String>> {

        @Override
        boolean check(Collection<String> portDefinitions1, Collection<String> portDefinitions2) {
            String[] defaultValues = {"0", "*"};
            boolean defaultDefinition1 = CollectionUtil.isEmpty(portDefinitions1) || (portDefinitions1.size() == 1 && contains(defaultValues, portDefinitions1.iterator().next()));
            boolean defaultDefinition2 = CollectionUtil.isEmpty(portDefinitions2) || (portDefinitions2.size() == 1 && contains(defaultValues, portDefinitions2.iterator().next()));
            return (defaultDefinition1 && defaultDefinition2) || nullSafeEqual(portDefinitions1, portDefinitions2);
        }
    }

    private static class InterfacesConfigChecker extends ConfigChecker<InterfacesConfig> {
        @Override
        boolean check(InterfacesConfig c1, InterfacesConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(new ArrayList<>(c1.getInterfaces()), new ArrayList<>(c2.getInterfaces())));
        }
    }

    private static class SSLConfigChecker extends ConfigChecker<SSLConfig> {
        @Override
        boolean check(SSLConfig c1, SSLConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(classNameOrImpl(c1.getFactoryClassName(), c1.getFactoryImplementation()), classNameOrImpl(c2.getFactoryClassName(), c2.getFactoryImplementation())) && nullSafeEqual(c1.getProperties(), c2.getProperties()));
        }
    }

    public static class AuditlogConfigChecker extends ConfigChecker<AuditlogConfig> {
        @Override
        boolean check(AuditlogConfig c1, AuditlogConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(c1.getFactoryClassName(), c2.getFactoryClassName()) && nullSafeEqual(c1.getProperties(), c2.getProperties()));
        }
    }

    private static class SocketInterceptorConfigChecker extends ConfigChecker<SocketInterceptorConfig> {
        @Override
        boolean check(SocketInterceptorConfig c1, SocketInterceptorConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(classNameOrImpl(c1.getClassName(), c1.getImplementation()), classNameOrImpl(c2.getClassName(), c2.getImplementation())) && nullSafeEqual(c1.getProperties(), c2.getProperties()));
        }
    }

    private static class SymmetricEncryptionConfigChecker extends ConfigChecker<SymmetricEncryptionConfig> {
        @Override
        boolean check(SymmetricEncryptionConfig c1, SymmetricEncryptionConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(c1.getSalt(), c2.getSalt()) && nullSafeEqual(c1.getPassword(), c2.getPassword())) && nullSafeEqual(c1.getIterationCount(), c2.getIterationCount()) && nullSafeEqual(c1.getAlgorithm(), c2.getAlgorithm()) && nullSafeEqual(c1.getKey(), c2.getKey());
        }
    }

    public static class DiscoveryConfigChecker extends ConfigChecker<DiscoveryConfig> {
        @Override
        public boolean check(DiscoveryConfig c1, DiscoveryConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nodeFilterClassNameEqual(c1, c2) && nullSafeEqual(c1.getDiscoveryServiceProvider(), c2.getDiscoveryServiceProvider()) && isCollectionCompatible(c1.getDiscoveryStrategyConfigs(), c2.getDiscoveryStrategyConfigs(), new DiscoveryStrategyConfigChecker()));
        }

        private boolean nodeFilterClassNameEqual(DiscoveryConfig c1, DiscoveryConfig c2) {
            return classNameOrImpl(c1.getNodeFilterClass(), c1.getNodeFilter()).equals(classNameOrImpl(c2.getNodeFilterClass(), c2.getNodeFilter()));
        }
    }

    public static class AliasedDiscoveryConfigsChecker extends ConfigChecker<List<AliasedDiscoveryConfig<?>>> {

        private static Map<String, AliasedDiscoveryConfig> mapByTag(List<AliasedDiscoveryConfig<?>> configs) {
            Map<String, AliasedDiscoveryConfig> result = new HashMap<>();
            for (AliasedDiscoveryConfig c : configs) {
                if (c.isEnabled()) {
                    result.put(c.getTag(), c);
                }
            }
            return result;
        }

        public static boolean check(AliasedDiscoveryConfig c1, AliasedDiscoveryConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(c1.getTag(), c2.getTag()) && nullSafeEqual(c1.isUsePublicIp(), c2.isUsePublicIp()) && nullSafeEqual(c1.getProperties(), c2.getProperties()));
        }

        @Override
        boolean check(List<AliasedDiscoveryConfig<?>> t1, List<AliasedDiscoveryConfig<?>> t2) {
            Map<String, AliasedDiscoveryConfig> m1 = mapByTag(t1);
            Map<String, AliasedDiscoveryConfig> m2 = mapByTag(t2);

            if (m1.size() != m2.size()) {
                return false;
            }

            for (String tag : m1.keySet()) {
                AliasedDiscoveryConfig c1 = m1.get(tag);
                AliasedDiscoveryConfig c2 = m2.get(tag);
                if (!check(c1, c2)) {
                    return false;
                }
            }

            return true;
        }
    }

    public static class WanReplicationConfigChecker extends ConfigChecker<WanReplicationConfig> {
        private static final WanConsumerConfigChecker WAN_CONSUMER_CONFIG_CHECKER = new WanConsumerConfigChecker();

        @Override
        public boolean check(WanReplicationConfig c1, WanReplicationConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getName(), c2.getName()) && WAN_CONSUMER_CONFIG_CHECKER.check(c1.getConsumerConfig(), c2.getConsumerConfig()) && isCollectionCompatible(c1.getBatchPublisherConfigs(), c2.getBatchPublisherConfigs(), new WanBatchPublisherConfigChecker()) && isCollectionCompatible(c1.getCustomPublisherConfigs(), c2.getCustomPublisherConfigs(), new WanCustomPublisherConfigChecker());
        }
    }

    public static class WanConsumerConfigChecker extends ConfigChecker<WanConsumerConfig> {
        @Override
        public boolean check(WanConsumerConfig c1, WanConsumerConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getClassName(), c2.getClassName()) && nullSafeEqual(c1.getImplementation(), c2.getImplementation()) && nullSafeEqual(c1.isPersistWanReplicatedData(), c2.isPersistWanReplicatedData()) && nullSafeEqual(c1.getProperties(), c2.getProperties());
        }
    }

    public static class WanCustomPublisherConfigChecker extends ConfigChecker<WanCustomPublisherConfig> {
        @Override
        public boolean check(WanCustomPublisherConfig c1, WanCustomPublisherConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getPublisherId(), c2.getPublisherId()) && nullSafeEqual(c1.getClassName(), c2.getClassName()) && nullSafeEqual(c1.getImplementation(), c2.getImplementation()) && nullSafeEqual(c1.getProperties(), c2.getProperties());
        }
    }

    public static class WanBatchPublisherConfigChecker extends ConfigChecker<WanBatchPublisherConfig> {
        @Override
        public boolean check(WanBatchPublisherConfig c1, WanBatchPublisherConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getClusterName(), c2.getClusterName()) && nullSafeEqual(c1.getPublisherId(), c2.getPublisherId()) && c1.isSnapshotEnabled() == c2.isSnapshotEnabled() && c1.getInitialPublisherState() == c2.getInitialPublisherState() && c1.getQueueCapacity() == c2.getQueueCapacity() && c1.getBatchSize() == c2.getBatchSize() && c1.getBatchMaxDelayMillis() == c2.getBatchMaxDelayMillis() && c1.getResponseTimeoutMillis() == c2.getResponseTimeoutMillis() && c1.getQueueFullBehavior() == c2.getQueueFullBehavior() && c1.getAcknowledgeType() == c2.getAcknowledgeType() && c1.getDiscoveryPeriodSeconds() == c2.getDiscoveryPeriodSeconds() && c1.getMaxTargetEndpoints() == c2.getMaxTargetEndpoints() && c1.getMaxConcurrentInvocations() == c2.getMaxConcurrentInvocations() && c1.isUseEndpointPrivateAddress() == c2.isUseEndpointPrivateAddress() && c1.getIdleMinParkNs() == c2.getIdleMinParkNs() && c1.getIdleMaxParkNs() == c2.getIdleMaxParkNs() && nullSafeEqual(c1.getTargetEndpoints(), c2.getTargetEndpoints()) && new AliasedDiscoveryConfigsChecker().check(aliasedDiscoveryConfigsFrom(c1), aliasedDiscoveryConfigsFrom(c2)) && new DiscoveryConfigChecker().check(c1.getDiscoveryConfig(), c2.getDiscoveryConfig()) && new WanSyncConfigChecker().check(c1.getSyncConfig(), c2.getSyncConfig()) && nullSafeEqual(c1.getEndpoint(), c2.getEndpoint()) && nullSafeEqual(c1.getImplementation(), c2.getImplementation()) && nullSafeEqual(c1.getProperties(), c2.getProperties());
        }
    }

    private static class PartitionGroupConfigChecker extends ConfigChecker<PartitionGroupConfig> {
        @Override
        boolean check(PartitionGroupConfig c1, PartitionGroupConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(c1.getGroupType(), c2.getGroupType()) && isCollectionCompatible(c1.getMemberGroupConfigs(), c2.getMemberGroupConfigs(), new MemberGroupConfigChecker()));
        }
    }

    private static class SerializationConfigChecker extends ConfigChecker<SerializationConfig> {
        private static boolean isCompatible(GlobalSerializerConfig c1, GlobalSerializerConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getClassName(), c2.getClassName()) && nullSafeEqual(c1.isOverrideJavaSerialization(), c2.isOverrideJavaSerialization());
        }

        @Override
        boolean check(SerializationConfig c1, SerializationConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getPortableVersion(), c2.getPortableVersion()) && nullSafeEqual(c1.getDataSerializableFactoryClasses(), c2.getDataSerializableFactoryClasses()) && nullSafeEqual(c1.getPortableFactoryClasses(), c2.getPortableFactoryClasses()) && isCompatible(c1.getGlobalSerializerConfig(), c2.getGlobalSerializerConfig()) && isCollectionCompatible(c1.getSerializerConfigs(), c2.getSerializerConfigs(), new SerializerConfigChecker()) && nullSafeEqual(c1.isCheckClassDefErrors(), c2.isCheckClassDefErrors()) && nullSafeEqual(c1.isUseNativeByteOrder(), c2.isUseNativeByteOrder()) && nullSafeEqual(c1.getByteOrder(), c2.getByteOrder()) && nullSafeEqual(c1.isEnableCompression(), c2.isEnableCompression()) && nullSafeEqual(c1.isEnableSharedObject(), c2.isEnableSharedObject()) && nullSafeEqual(c1.isAllowUnsafe(), c2.isAllowUnsafe()) && nullSafeEqual(c1.isAllowOverrideDefaultSerializers(), c2.isAllowOverrideDefaultSerializers()) && nullSafeEqual(c1.getJavaSerializationFilterConfig(), c2.getJavaSerializationFilterConfig()) && nullSafeEqual(c1.getCompactSerializationConfig(), c2.getCompactSerializationConfig());
        }
    }

    private static class ServicesConfigChecker extends ConfigChecker<ServicesConfig> {
        private static boolean isCompatible(Collection<ServiceConfig> c1, Collection<ServiceConfig> c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null || c1.size() != c2.size()) {
                return false;
            }

            HashMap<String, ServiceConfig> config1 = new HashMap<>();
            HashMap<String, ServiceConfig> config2 = new HashMap<>();

            for (ServiceConfig serviceConfig : c1) {
                config1.put(serviceConfig.getName(), serviceConfig);
            }
            for (ServiceConfig serviceConfig : c2) {
                config2.put(serviceConfig.getName(), serviceConfig);
            }

            if (!config1.keySet().equals(config2.keySet())) {
                return false;
            }

            for (ServiceConfig serviceConfig : c1) {
                if (!isCompatible(serviceConfig, config2.get(serviceConfig.getName()))) {
                    return false;
                }
            }
            return true;
        }

        private static boolean isCompatible(ServiceConfig c1, ServiceConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getClassName(), c2.getClassName()) && nullSafeEqual(c1.getImplementation(), c2.getImplementation()) && nullSafeEqual(c1.getProperties(), c2.getProperties()) && nullSafeEqual(c1.getConfigObject(), c2.getConfigObject()));
        }

        @Override
        boolean check(ServicesConfig c1, ServicesConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.isEnableDefaults(), c2.isEnableDefaults()) && isCompatible(c1.getServiceConfigs(), c2.getServiceConfigs());
        }
    }

    private static class SecurityConfigChecker extends ConfigChecker<SecurityConfig> {

        private static boolean isCompatible(Map<String, RealmConfig> c1, Map<String, RealmConfig> c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null || c1.size() != c2.size()) {
                return false;
            }
            for (String realmName : c1.keySet()) {
                if (!isCompatible(c1.get(realmName), c2.get(realmName))) {
                    return false;
                }
            }
            return true;
        }

        private static boolean isCompatible(RealmConfig c1, RealmConfig c2) {
            if (c1 == null || c2 == null) {
                return false;
            }
            return isCompatible(c1.getCredentialsFactoryConfig(), c2.getCredentialsFactoryConfig()) && isCompatible(c1.getJaasAuthenticationConfig(), c2.getJaasAuthenticationConfig()) && isCompatible(c1.getTlsAuthenticationConfig(), c2.getTlsAuthenticationConfig()) && isCompatible(c1.getLdapAuthenticationConfig(), c2.getLdapAuthenticationConfig()) && isCompatible(c1.getKerberosAuthenticationConfig(), c2.getKerberosAuthenticationConfig()) && isCompatible(c1.getUsernamePasswordIdentityConfig(), c2.getUsernamePasswordIdentityConfig()) && isCompatible(c1.getTokenIdentityConfig(), c2.getTokenIdentityConfig()) && isCompatible(c1.getKerberosIdentityConfig(), c2.getKerberosIdentityConfig()) && isCompatible(c1.getAccessControlServiceConfig(), c2.getAccessControlServiceConfig());
        }

        private static boolean isCompatible(AccessControlServiceConfig c1, AccessControlServiceConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && nullSafeEqual(c1.getFactoryClassName(), c2.getFactoryClassName()) && nullSafeEqual(c1.getProperties(), c2.getProperties()));
        }

        private static boolean isCompatible(UsernamePasswordIdentityConfig c1, UsernamePasswordIdentityConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && nullSafeEqual(c1.getUsername(), c2.getUsername()) && nullSafeEqual(c1.getPassword(), c2.getPassword()));
        }

        private static boolean isCompatible(TokenIdentityConfig c1, TokenIdentityConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && c1.equals(c2));
        }

        private static boolean isCompatible(TlsAuthenticationConfig c1, TlsAuthenticationConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && nullSafeEqual(c1.getRoleAttribute(), c2.getRoleAttribute()));
        }

        private static boolean isCompatible(LdapAuthenticationConfig c1, LdapAuthenticationConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && c1.equals(c2));
        }

        private static boolean isCompatible(KerberosAuthenticationConfig c1, KerberosAuthenticationConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && c1.equals(c2));
        }

        private static boolean isCompatible(KerberosIdentityConfig c1, KerberosIdentityConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && c1.equals(c2));
        }

        private static boolean isCompatible(JaasAuthenticationConfig c1, JaasAuthenticationConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && isCompatibleLoginModule(c1.getLoginModuleConfigs(), c2.getLoginModuleConfigs()));
        }

        private static boolean isCompatible(CredentialsFactoryConfig c1, CredentialsFactoryConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getProperties(), c2.getProperties()) && nullSafeEqual(c1.getClassName(), c2.getClassName()) && nullSafeEqual(c1.getImplementation(), c2.getImplementation());
        }

        private static boolean isCompatibleLoginModule(List<LoginModuleConfig> c1, List<LoginModuleConfig> c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null || c1.size() != c2.size()) {
                return false;
            }

            Map<String, LoginModuleConfig> config1 = new HashMap<>();
            Map<String, LoginModuleConfig> config2 = new HashMap<>();

            for (LoginModuleConfig loginModuleConfig : c1) {
                config1.put(loginModuleConfig.getClassName(), loginModuleConfig);
            }
            for (LoginModuleConfig loginModuleConfig : c2) {
                config2.put(loginModuleConfig.getClassName(), loginModuleConfig);
            }

            if (!config1.keySet().equals(config2.keySet())) {
                return false;
            }

            for (LoginModuleConfig a : c1) {
                LoginModuleConfig b = config2.get(a.getClassName());

                if (!(a == b || (nullSafeEqual(a.getProperties(), b.getProperties()) && nullSafeEqual(a.getUsage(), b.getUsage())))) {
                    return false;
                }
            }

            return true;
        }

        private static boolean isCompatible(List<SecurityInterceptorConfig> c1, List<SecurityInterceptorConfig> c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null || c1.size() != c2.size()) {
                return false;
            }

            Map<String, SecurityInterceptorConfig> config1 = new HashMap<>();
            Map<String, SecurityInterceptorConfig> config2 = new HashMap<>();

            for (SecurityInterceptorConfig securityInterceptorConfig : c1) {
                config1.put(securityInterceptorConfig.getClassName(), securityInterceptorConfig);
            }
            for (SecurityInterceptorConfig securityInterceptorConfig : c2) {
                config2.put(securityInterceptorConfig.getClassName(), securityInterceptorConfig);
            }

            if (!config1.keySet().equals(config2.keySet())) {
                return false;
            }

            for (SecurityInterceptorConfig a : c1) {
                SecurityInterceptorConfig b = config2.get(a.getClassName());

                if (!(a == b || nullSafeEqual(a.getImplementation(), b.getImplementation()))) {
                    return false;
                }
            }

            return true;
        }

        private static boolean isCompatible(PermissionPolicyConfig c1, PermissionPolicyConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getProperties(), c2.getProperties()) && nullSafeEqual(c1.getClassName(), c2.getClassName()) && nullSafeEqual(c1.getImplementation(), c2.getImplementation());
        }

        private static boolean isCompatible(Set<PermissionConfig> c1, Set<PermissionConfig> c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null || c1.size() != c2.size()) {
                return false;
            }

            List<PermissionConfig> configs1 = asList(c1.toArray());
            List<PermissionConfig> configs2 = asList(c2.toArray());

            for (PermissionConfig a : configs1) {
                if (!configs2.contains(a)) {
                    return false;
                }
            }

            return true;
        }

        @Override
        boolean check(SecurityConfig c1, SecurityConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.isEnabled(), c2.isEnabled()) && nullSafeEqual(c1.isPermissionPriorityGrant(), c2.isPermissionPriorityGrant()) && (c1.getOnJoinPermissionOperation() == c2.getOnJoinPermissionOperation()) && nullSafeEqual(c1.getClientBlockUnmappedActions(), c2.getClientBlockUnmappedActions()) && nullSafeEqual(c1.getClientRealm(), c2.getClientRealm()) && nullSafeEqual(c1.getMemberRealm(), c2.getMemberRealm()) && isCompatible(c1.getRealmConfigs(), c2.getRealmConfigs()) && isCompatible(c1.getSecurityInterceptorConfigs(), c2.getSecurityInterceptorConfigs()) && isCompatible(c1.getClientPolicyConfig(), c2.getClientPolicyConfig()) && isCompatible(c1.getClientPermissionConfigs(), c2.getClientPermissionConfigs());
        }
    }

    private static class ManagementCenterConfigChecker extends ConfigChecker<ManagementCenterConfig> {
        @Override
        boolean check(ManagementCenterConfig c1, ManagementCenterConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && (c1.isScriptingEnabled() == c2.isScriptingEnabled()) && (c1.isConsoleEnabled() == c2.isConsoleEnabled()) && (c1.isDataAccessEnabled() == c2.isDataAccessEnabled())) && nullSafeEqual(c1.getTrustedInterfaces(), c2.getTrustedInterfaces());
        }
    }

    private static class HotRestartConfigChecker extends ConfigChecker<HotRestartPersistenceConfig> {
        @Override
        boolean check(HotRestartPersistenceConfig c1, HotRestartPersistenceConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(c1.getBaseDir(), c2.getBaseDir()) && nullSafeEqual(c1.getBackupDir(), c2.getBackupDir()) && nullSafeEqual(c1.getParallelism(), c2.getParallelism()) && nullSafeEqual(c1.getValidationTimeoutSeconds(), c2.getValidationTimeoutSeconds()) && nullSafeEqual(c1.getDataLoadTimeoutSeconds(), c2.getDataLoadTimeoutSeconds()) && nullSafeEqual(c1.getClusterDataRecoveryPolicy(), c2.getClusterDataRecoveryPolicy()) && nullSafeEqual(c1.getEncryptionAtRestConfig(), c2.getEncryptionAtRestConfig()));
        }
    }

    private static class PersistenceConfigChecker extends ConfigChecker<PersistenceConfig> {
        @Override
        boolean check(PersistenceConfig c1, PersistenceConfig c2) {
            boolean c1Disabled = c1 == null || !c1.isEnabled();
            boolean c2Disabled = c2 == null || !c2.isEnabled();
            return c1 == c2 || (c1Disabled && c2Disabled) || (c1 != null && c2 != null && nullSafeEqual(c1.getBaseDir(), c2.getBaseDir()) && nullSafeEqual(c1.getBackupDir(), c2.getBackupDir()) && nullSafeEqual(c1.getParallelism(), c2.getParallelism()) && nullSafeEqual(c1.getValidationTimeoutSeconds(), c2.getValidationTimeoutSeconds()) && nullSafeEqual(c1.getDataLoadTimeoutSeconds(), c2.getDataLoadTimeoutSeconds()) && nullSafeEqual(c1.getRebalanceDelaySeconds(), c2.getRebalanceDelaySeconds()) && nullSafeEqual(c1.getClusterDataRecoveryPolicy(), c2.getClusterDataRecoveryPolicy()) && nullSafeEqual(c1.getEncryptionAtRestConfig(), c2.getEncryptionAtRestConfig()));
        }
    }

    private static class LocalDeviceConfigChecker extends ConfigChecker<LocalDeviceConfig> {

        @Override
        boolean check(LocalDeviceConfig t1, LocalDeviceConfig t2) {
            return Objects.equals(t1, t2);
        }
    }

    private static class CRDTReplicationConfigChecker extends ConfigChecker<CRDTReplicationConfig> {
        @Override
        boolean check(CRDTReplicationConfig c1, CRDTReplicationConfig c2) {
            return c1 == c2 || (c1 != null && c2 != null && nullSafeEqual(c1.getMaxConcurrentReplicationTargets(), c2.getMaxConcurrentReplicationTargets()) && nullSafeEqual(c1.getReplicationPeriodMillis(), c2.getReplicationPeriodMillis()));
        }
    }

    static class SplitBrainProtectionConfigChecker extends ConfigChecker<SplitBrainProtectionConfig> {
        @Override
        boolean check(SplitBrainProtectionConfig c1, SplitBrainProtectionConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }

            return ((c1.isEnabled() == c2.isEnabled()) && nullSafeEqual(c1.getName(), c2.getName()) && nullSafeEqual(c1.getProtectOn(), c2.getProtectOn()) && (c1.getMinimumClusterSize() == c2.getMinimumClusterSize()) && nullSafeEqual(c1.getFunctionClassName(), c2.getFunctionClassName()) && nullSafeEqual(c1.getFunctionImplementation(), c2.getFunctionImplementation()) && nullSafeEqual(c1.getListenerConfigs(), c2.getListenerConfigs()));
        }
    }

    public static class RestApiConfigChecker extends ConfigChecker<RestApiConfig> {
        @Override
        public boolean check(RestApiConfig c1, RestApiConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }
            return (c1.isEnabled() == c2.isEnabled()) && nullSafeEqual(c1.getEnabledGroups(), c2.getEnabledGroups());
        }
    }

    public static class MemcacheProtocolConfigChecker extends ConfigChecker<MemcacheProtocolConfig> {
        @Override
        public boolean check(MemcacheProtocolConfig c1, MemcacheProtocolConfig c2) {
            if (c1 == c2) {
                return true;
            }
            if (c1 == null || c2 == null) {
                return false;
            }
            return (c1.isEnabled() == c2.isEnabled());
        }
    }

    public static class PartitioningAttributesConfigChecker extends ConfigChecker<PartitioningAttributeConfig> {
        @Override
        boolean check(PartitioningAttributeConfig c1, PartitioningAttributeConfig c2) {
            return c1 == c2 || !(c1 == null || c2 == null) && nullSafeEqual(c1.getAttributeName(), c2.getAttributeName());
        }
    }

    private static class VectorCollectionConfigsChecker extends ConfigChecker<Map<String, VectorCollectionConfig>> {
        @Override
        boolean check(Map<String, VectorCollectionConfig> t1, Map<String, VectorCollectionConfig> t2) {
            return Objects.equals(t1, t2);
        }
    }
}
